import flask
import json
import jwt
import requests as rq
from functools import wraps, partial
from .errors import UnauthorizedError, BadRequestError, ServerError, UnknownAPIClientError


API_ENDPOINT = 'http://localhost:8080/api'


def _get_access_token_from_request():
    return flask.request.cookies.get('access_token', '')


def _with_api_client(fun):

    def wrapper(*args, **kwargs):
        token = _get_access_token_from_request()
        client = APIClient(token)
        return fun(client, *args, **kwargs)

    return wrapper


def _guard_request(fun):

    @wraps(fun)
    def wrapper(self, *args, **kwargs):
        if not self.token:
            raise UnauthorizedError()
        else:
            response = fun(self, *args, **kwargs)
            code = response.status_code
            if code == 401:
                raise UnauthorizedError(response.text)
            if code == 400:
                raise BadRequestError(response.text)
            if code >= 500:
                raise ServerError(response.text)
            if 100 < code < 300:
                try:
                    return response.json(), response.status_code
                except json.decoder.JSONDecodeError:
                    return response.text, response.status_code
        raise UnknownAPIClientError(f"{code}: {response.text}")

    return wrapper


class BearerAuth(rq.auth.AuthBase):

    def __init__(self, token):
        self.token = token

    def __call__(self, r):
        r.headers['authorization'] = 'Bearer ' + self.token
        return r


class APIClient:

    def __init__(self, token=None):
        self.token = token
        self.endpoint = API_ENDPOINT

        # Create dynamic CRUD interfaces
        api_mapping = {
            'task': '/tasks/',
            'container': '/shipping-containers/',
            'user': '/users/'
        }

        for resource, path in api_mapping.items():
            for action in ['create', 'update', 'retrieve', 'destroy', 'list']:
                handler = partial(self._crud_template, action, path)
                setattr(self, f'{action}_{resource}', handler)

    @_guard_request
    def _crud_template(self, action, path, id=None, payload=None):
        action_verb_mapping = {
            'retrieve': 'get',
            'list': 'get',
            'create': 'post',
            'destroy': 'delete',
            'update': 'patch',
        }

        method = getattr(self.session, action_verb_mapping[action])
        if action in ('retrieve', 'update', 'destroy'):
            path += str(id) + '/'

        method_args = dict()
        if payload:
            method_args['json'] = payload

        return method(self.endpoint + path, **method_args)

    @property
    def session(self):
        sess = rq.Session()
        if self.token:
            sess.auth = BearerAuth(self.token)
        return sess

    def login(self, email, password):
        return self.session.post(self.endpoint + '/login/', json={'email': email,
                                                                  'password': password})

    def register(self, email, password, firstname, lastname):
        return self.session.post(self.endpoint + '/register/', json={'email': email,
                                                                     'password': password,
                                                                     'first_name': firstname,
                                                                     'last_name': lastname})


def is_user_authenticated():
    try:
        get_user()
        return True
    except Exception:
        return False


# ----
# Misc
def read_token_body():
    token = _get_access_token_from_request()
    return jwt.decode(token, options={
        'verify_signature': False
    })


# ----
# User
@_with_api_client
def get_user(client):
    payload, _ = client.list_user()
    return payload


@_with_api_client
def update_user(client, id, payload):
    payload, _ = client.update_user(id=id, payload=payload)
    return payload

# ----
# Task
@_with_api_client
def list_task(client):
    payload, _ = client.list_task()
    return payload


@_with_api_client
def create_task(client, payload):
    payload, _ = client.create_task(payload=payload)
    return payload


@_with_api_client
def destroy_task(client, id):
    payload, _ = client.destroy_task(id=id)
    return payload


@_with_api_client
def retrieve_task(client, id):
    payload, _ = client.retrieve_task(id=id)
    return payload


@_with_api_client
def update_task(client, id, payload):
    payload, _ = client.update_task(id=id, payload=payload)
    return payload


# --------
# Container
@_with_api_client
def destroy_container(client, id):
    payload, _ = client.destroy_container(id=id)
    return payload


@_with_api_client
def update_container(client, id, payload):
    payload, _ = client.update_container(id=id, payload=payload)
    return payload


# Container CRUD
@_with_api_client
def list_container(client):
    payload, _ = client.list_container()
    return payload


@_with_api_client
def create_container(client, payload):
    payload, _ = client.create_container(payload=payload)
    return payload


@_with_api_client
def destroy_container(client, id):
    payload, _ = client.destroy_container(id=id)
    return payload
